#include <user.h>

#define IPS_NODE_SIZE 12

static char __ips_size_assert[sizeof(ips_node_t) == IPS_NODE_SIZE ? 0 : -1];
static char __mutex_size_assert[sizeof(mutex_t) == 12 ? 0 : -1];

void
ips_wait(ips_node_t *node)
{
	 fiber_wait_pretend();
	 while (1)
	 {
		  if (!node->wait || !node->ac_wait)
			   break;
		  fiber_wait_try();
	 }
}

int
ips_wait_try(ips_node_t *node)
{
	 return node->wait && node->ac_wait;
}

int
ips_is_finish(ips_node_t *node)
{
	 return !node->wait;
}

#define FIBER_TO_ID(node) ((uintptr_t)(node) >> 2)
#define ID_TO_FIBER(node) ((fiber_t)((uintptr_t)(node) << 2))

#define NODE_PTR_TO_ID(node) ((uintptr_t)(node) >> 2)
#define NODE_ID_TO_PTR(node) ((ips_node_t *)((uintptr_t)(node) << 2))

void
mutex_init(mutex_t *mutex)
{
	 spl_init(&mutex->lock);
	 mutex->hold = 0;
	 mutex->wait_count = 0;
}

int
mutex_try_acquire(mutex_t *mutex)
{
	 int result;
	 while (spl_try_acquire(&mutex->lock)) fiber_yield_low();
	 
	 if (mutex->hold == 0)
	 {
		  mutex->hold = 1;
		  mutex->wait = 0;
		  
		  result = 0;
	 }
	 else result = 1;
	 spl_release(&mutex->lock);
	 
	 return result;
}

int
mutex_acquire(mutex_t *mutex, ips_node_t *node, fiber_t fiber)
{
	 if (node == NULL)
	 {
		  ips_node_t node;
		  if (mutex_acquire(mutex, &node, fiber))
		  {
			   // kprintf("mutex %08x wait, fiber %08x\n", mutex, ID_TO_FIBER(node.fiber));
			   ips_wait(&node);
			   // kprintf("mutex %08x wait finish, fiber %08x, %d %d\n",
			   // mutex, ID_TO_FIBER(node.fiber),
			   // mutex->hold, mutex->wait);
		  }

		  return 0;
	 }
	 else
	 {
		  while (spl_try_acquire(&mutex->lock)) fiber_yield_low();
		  
		  if (mutex->hold == 0)
		  {
			   mutex->hold = 1;
			   mutex->wait = 0;
			   
			   spl_release(&mutex->lock);
			   
			   node->wait = 0;
			   
			   return 0;
		  }
		  else
		  {
			   node->fiber   = fiber != NULL ? FIBER_TO_ID(fiber) : FIBER_TO_ID(fiber_self());
			   node->ac_wait = 1;
			   node->wait    = 1;

			   ++ mutex->wait_count;
			   if (mutex->wait)
			   {
					node->next = NODE_ID_TO_PTR(mutex->head);
					node->prev = node->next->prev;
					node->next->prev = node;
					node->prev->next = node;
			   }
			   else
			   {
					mutex->wait = 1;
					node->next = node->prev = node;
					mutex->head = NODE_PTR_TO_ID(node);
			   }
			   
			   spl_release(&mutex->lock);
			   return 1;
		  }
	 }
}

void
mutex_ac_break(mutex_t *mutex, ips_node_t *node)
{
	 while (spl_try_acquire(&mutex->lock)) fiber_yield_low();

	 if (node->wait)
	 {
		  node->next->prev = node->next;
		  node->prev->next = node->prev;
		  
		  if (mutex->head == NODE_PTR_TO_ID(node))
		  {
			   if (node->next == node)
					mutex->wait = 0;
			   else mutex->head = NODE_PTR_TO_ID(node->next);
		  }
	 }
	 spl_release(&mutex->lock);
	 
	 node->ac_wait = 0;
}

void
mutex_release(mutex_t *mutex)
{
	 fiber_t notify = NULL;
	 if (!mutex->hold) return;

	 while (spl_try_acquire(&mutex->lock)) fiber_yield_low();

	 if (mutex->wait)
	 {
		  ips_node_t *node = NODE_ID_TO_PTR(mutex->head);
		  node->wait = 0;
		  
		  notify = ID_TO_FIBER(node->fiber);
		  node->next->prev = node->prev;
		  node->prev->next = node->next;
		  
		  if (mutex->head == NODE_PTR_TO_ID(node->next))
			   mutex->wait = 0;
		  else mutex->head = NODE_PTR_TO_ID(node->next);
	 }
	 else mutex->hold = 0;
	 spl_release(&mutex->lock);
	 if (notify != NULL)
	 {
		  // kprintf("mutex %08x, fiber %08x, notify %08x\n", mutex, fiber_self(), notify);
		  fiber_notify(notify);
	 }
}
